From 5d6c0649ddf534d574f7c64245f13a9924400245 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C3=89tienne=20Mollier?= Date: Wed, 3 Jun 2026 19:24:07 +0200 Subject: [PATCH] CVE-2026-10528-partial.patch: new: fix needed by orthanc. This patch introduce the part of the mitigation against CVE-2026-10528 affecting orthanc that needs to be applied on the side of dcmtk. See also Debian bug #1138713. --- debian/patches/CVE-2026-10528-partial.patch | 1358 +++++++++++++++++++ debian/patches/series | 1 + 2 files changed, 1359 insertions(+) create mode 100644 debian/patches/CVE-2026-10528-partial.patch diff --git a/debian/patches/CVE-2026-10528-partial.patch b/debian/patches/CVE-2026-10528-partial.patch new file mode 100644 index 00000000..29a10891 --- /dev/null +++ b/debian/patches/CVE-2026-10528-partial.patch @@ -0,0 +1,1358 @@ +Description: Add sequence nesting depth limit for parsing + Track sequence nesting depth on DcmInputStream during parsing. When the + depth exceeds a configurable limit, parsing is aborted with + EC_NestingDepthLimitExceeded. This prevents stack overflow from + excessively nested DICOM sequences. + . + The default limit is controlled by the compile-time macro + DCMTK_MAX_SEQUENCE_NESTING (default: 64). It can be overridden per parse + operation via the runtime API: + . + - DcmInputStream::setMaxNestingDepth() for direct stream access + - DcmItem::setMaxNestingDepth() (inherited by DcmDataset) for loadFile() + and read() + - DcmFileFormat::setMaxNestingDepth() for loadFile() and read(), + forwarded to the contained dataset + - DcmSCP::setMaxNestingDepth() and DcmSCU::setMaxNestingDepth() for + datasets received over the network via receiveDIMSEDataset() + . + The runtime setter uses Sint32 semantics: + 0 = compile-time default, -1 = unlimited, >0 = limit + . + Note: the specific EC_NestingDepthLimitExceeded error code is not yet + surfaced through the DIMSE layer; DIMSE_receiveDataSetInMemory() maps + all parse errors to DIMSE_RECEIVEFAILED. + . + Thanks to the IN-CYPHER OSS Security Team for the report, detailed + analysis and proof of concept. + . + This closes DCMTK Bug #1191. +Author: Michael Onken +Applied-Upstream: 885ff0f10372bd589b5f44cea974f28a3964cb0f +Last-Update: 2026-04-11 +Bug: https://support.dcmtk.org/redmine/issues/1191 +Bug-Debian: https://bugs.debian.org/1138713 +Reviewed-By: Étienne Mollier + +--- dcmtk.orig/config/docs/macros.txt ++++ dcmtk/config/docs/macros.txt +@@ -109,6 +109,21 @@ + dcmtk::log4cplus::threadCleanup() should be called by the user code in + order to clean-up oflog's thread local storage. + ++DCMTK_MAX_SEQUENCE_NESTING ++ Affected: dcmdata ++ Type of modification: Compile-time tunable ++ Explanation: Defines the default maximum permitted sequence nesting depth ++ during DICOM parsing. Deeply nested sequences can cause a stack overflow ++ by exhausting the call stack through unbounded recursion. When this macro ++ is not defined, the default limit of 64 nested sequence levels is used. ++ Real-world DICOM data rarely exceeds 5-10 nesting levels. The limit can ++ be changed at runtime per parse operation via ++ DcmInputStream::setMaxNestingDepth(), DcmItem::setMaxNestingDepth(), or ++ DcmFileFormat::setMaxNestingDepth(). Setting the runtime value to -1 ++ disables the check entirely. ++ Minimum value: 1. Values less than 1 cause a compile-time error. ++ Maximum value: 2147483647 (aligned to 32-bit signed integer runtime API). ++ + DCMTK_MERGE_STDERR_TO_STDOUT + Affected: dcmdata + Type of modification: Activates feature +--- dcmtk.orig/dcmdata/include/dcmtk/dcmdata/dcerror.h ++++ dcmtk/dcmdata/include/dcmtk/dcmdata/dcerror.h +@@ -1,6 +1,6 @@ + /* + * +- * Copyright (C) 1994-2025, OFFIS e.V. ++ * Copyright (C) 1994-2026, OFFIS e.V. + * All rights reserved. See COPYRIGHT file for details. + * + * This software and supporting documentation were developed by +@@ -200,6 +200,8 @@ + extern DCMTK_DCMDATA_EXPORT const OFConditionConst EC_UnsupportedURIType; + /// Execution of command line failed + extern DCMTK_DCMDATA_EXPORT const OFConditionConst EC_CommandLineFailed; ++/// Maximum sequence nesting depth exceeded (stack overflow protection) ++extern DCMTK_DCMDATA_EXPORT const OFConditionConst EC_NestingDepthLimitExceeded; + + ///@} + +--- dcmtk.orig/dcmdata/include/dcmtk/dcmdata/dcfilefo.h ++++ dcmtk/dcmdata/include/dcmtk/dcmdata/dcfilefo.h +@@ -514,6 +514,35 @@ + + /// implementation version name to write in the meta-header + OFString ImplementationVersionName; ++ ++ /// maximum sequence nesting depth for parsing ++ /// (0 = compile-time default DCMTK_MAX_SEQUENCE_NESTING, -1 = unlimited) ++ Sint32 MaxNestingDepth; ++ ++ public: ++ ++ /** set the maximum permitted sequence nesting depth for parsing. ++ * Applied to the input stream in loadFile() and read(), and also ++ * forwarded to the contained dataset. ++ * - Value 0 (default): apply the compile-time default ++ * (DCMTK_MAX_SEQUENCE_NESTING, default is 64) ++ * - Value -1: disable the check (allow unlimited nesting) ++ * - Value > 0: use this value as the maximum permitted nesting depth ++ * @param maxDepth maximum nesting depth setting ++ */ ++ void setMaxNestingDepth(Sint32 maxDepth) ++ { ++ MaxNestingDepth = maxDepth; ++ DcmDataset* dset = getDataset(); ++ if (dset) ++ dset->setMaxNestingDepth(maxDepth); ++ } ++ ++ /** return the maximum permitted sequence nesting depth for parsing. ++ * @return maximum nesting depth setting ++ * (0 = compile-time default DCMTK_MAX_SEQUENCE_NESTING, -1 = unlimited) ++ */ ++ Sint32 getMaxNestingDepth() const { return MaxNestingDepth; } + }; + + +--- dcmtk.orig/dcmdata/include/dcmtk/dcmdata/dcistrma.h ++++ dcmtk/dcmdata/include/dcmtk/dcmdata/dcistrma.h +@@ -1,6 +1,6 @@ + /* + * +- * Copyright (C) 1994-2018, OFFIS e.V. ++ * Copyright (C) 1994-2026, OFFIS e.V. + * All rights reserved. See COPYRIGHT file for details. + * + * This software and supporting documentation were developed by +@@ -224,6 +224,40 @@ + */ + virtual DcmInputStreamFactory *newFactory() const = 0; + ++ /** returns the current sequence nesting depth. ++ * This counter is used to prevent stack overflow from deeply nested ++ * DICOM sequences in malicious input files. ++ * @return current nesting depth ++ */ ++ Uint32 nestingDepth() const; ++ ++ /** increments the sequence nesting depth counter. ++ * @return the new nesting depth after incrementing ++ */ ++ Uint32 incrementNestingDepth(); ++ ++ /** decrements the sequence nesting depth counter. ++ * Does nothing if the counter is already zero. ++ */ ++ void decrementNestingDepth(); ++ ++ /** returns the maximum permitted sequence nesting depth for this stream. ++ * A value of 0 means the compile-time default (DCMTK_MAX_SEQUENCE_NESTING) applies. ++ * A value of -1 means the check is disabled (unlimited nesting). ++ * @return maximum nesting depth setting ++ */ ++ Sint32 maxNestingDepth() const; ++ ++ /** sets the maximum permitted sequence nesting depth for this stream. ++ * Must be called before parsing begins. ++ * - Value 0 (default): apply the compile-time default ++ * (DCMTK_MAX_SEQUENCE_NESTING, default is 64) ++ * - Value -1: disable the check (allow unlimited nesting) ++ * - Value > 0: use this value as the maximum permitted nesting depth ++ * @param maxDepth maximum nesting depth setting ++ */ ++ void setMaxNestingDepth(Sint32 maxDepth); ++ + /** marks the current stream position for a later putback operation, + * overwriting a possibly existing prior putback mark. + * The DcmObject read methods rely on the possibility to putback +@@ -272,6 +306,12 @@ + + /// putback marker + offile_off_t mark_; ++ ++ /// current sequence nesting depth (for stack overflow protection) ++ Uint32 nestingDepth_; ++ ++ /// maximum permitted sequence nesting depth (0 = default 64, -1 = unlimited) ++ Sint32 maxNestingDepth_; + }; + + +--- dcmtk.orig/dcmdata/include/dcmtk/dcmdata/dcitem.h ++++ dcmtk/dcmdata/include/dcmtk/dcmdata/dcitem.h +@@ -1,6 +1,6 @@ + /* + * +- * Copyright (C) 1994-2025, OFFIS e.V. ++ * Copyright (C) 1994-2026, OFFIS e.V. + * All rights reserved. See COPYRIGHT file for details. + * + * This software and supporting documentation were developed by +@@ -1573,6 +1573,29 @@ + + /// cache for private creator tags and identifiers + DcmPrivateTagCache privateCreatorCache; ++ ++ /// maximum sequence nesting depth for parsing ++ /// (0 = compile-time default DCMTK_MAX_SEQUENCE_NESTING, -1 = unlimited) ++ Sint32 maxNestingDepth; ++ ++ public: ++ ++ /** set the maximum permitted sequence nesting depth for parsing. ++ * This limit is applied to the input stream before parsing in ++ * loadFile() and read(). ++ * - Value 0 (default): apply the compile-time default ++ * (DCMTK_MAX_SEQUENCE_NESTING, default is 64) ++ * - Value -1: disable the check (allow unlimited nesting) ++ * - Value > 0: use this value as the maximum permitted nesting depth ++ * @param maxDepth maximum nesting depth setting ++ */ ++ void setMaxNestingDepth(Sint32 maxDepth) { maxNestingDepth = maxDepth; } ++ ++ /** return the maximum permitted sequence nesting depth for parsing. ++ * @return maximum nesting depth setting ++ * (0 = compile-time default DCMTK_MAX_SEQUENCE_NESTING, -1 = unlimited) ++ */ ++ Sint32 getMaxNestingDepth() const { return maxNestingDepth; } + }; + + /** Checks whether left hand side item is smaller than right hand side +--- dcmtk.orig/dcmdata/include/dcmtk/dcmdata/dcobject.h ++++ dcmtk/dcmdata/include/dcmtk/dcmdata/dcobject.h +@@ -1,6 +1,6 @@ + /* + * +- * Copyright (C) 1994-2024, OFFIS e.V. ++ * Copyright (C) 1994-2026, OFFIS e.V. + * All rights reserved. See COPYRIGHT file for details. + * + * This software and supporting documentation were developed by +@@ -50,6 +50,18 @@ + + // Undefined Length Identifier now defined in dctypes.h + ++// Default maximum sequence nesting depth (can be overridden at compile time). ++// Must be in the range [1, 2147483647]. ++#ifndef DCMTK_MAX_SEQUENCE_NESTING ++#define DCMTK_MAX_SEQUENCE_NESTING 64 ++#endif ++#if DCMTK_MAX_SEQUENCE_NESTING < 1 ++#error "DCMTK_MAX_SEQUENCE_NESTING must be >= 1" ++#endif ++#if DCMTK_MAX_SEQUENCE_NESTING > 2147483647 ++#error "DCMTK_MAX_SEQUENCE_NESTING must be <= 2147483647" ++#endif ++ + // Maximum number of read bytes for a Value Element + const Uint32 DCM_MaxReadLength = 4096; + +--- dcmtk.orig/dcmdata/libsrc/dcdatset.cc ++++ dcmtk/dcmdata/libsrc/dcdatset.cc +@@ -647,6 +647,9 @@ + { + /* use stdin stream */ + DcmStdinStream inStream; ++ /* apply configured nesting depth limit */ ++ if (getMaxNestingDepth() > 0) ++ inStream.setMaxNestingDepth(getMaxNestingDepth()); + + /* clear this object */ + l_error = clear(); +@@ -670,6 +673,9 @@ + } else { + /* open file for input */ + DcmInputFileStream fileStream(fileName); ++ /* apply configured nesting depth limit */ ++ if (getMaxNestingDepth() > 0) ++ fileStream.setMaxNestingDepth(getMaxNestingDepth()); + + /* check stream status */ + l_error = fileStream.status(); +--- dcmtk.orig/dcmdata/libsrc/dcerror.cc ++++ dcmtk/dcmdata/libsrc/dcerror.cc +@@ -91,6 +91,7 @@ + makeOFConditionConst(EC_BulkDataURINotSupported, OFM_dcmdata, 66, OF_error, "BulkDataURI not yet supported" ); + makeOFConditionConst(EC_UnsupportedURIType, OFM_dcmdata, 67, OF_error, "Unsupported URI type" ); + makeOFConditionConst(EC_CommandLineFailed, OFM_dcmdata, 68, OF_error, "Execution of command line failed" ); ++makeOFConditionConst(EC_NestingDepthLimitExceeded, OFM_dcmdata, 69, OF_error, "Maximum sequence nesting depth exceeded" ); + + const unsigned short EC_CODE_CannotSelectCharacterSet = 35; + const unsigned short EC_CODE_CannotConvertCharacterSet = 36; +--- dcmtk.orig/dcmdata/libsrc/dcfilefo.cc ++++ dcmtk/dcmdata/libsrc/dcfilefo.cc +@@ -54,7 +54,8 @@ + : DcmSequenceOfItems(DCM_InternalUseTag), + FileReadMode(ERM_autoDetect), + ImplementationClassUID(OFFIS_IMPLEMENTATION_CLASS_UID), +- ImplementationVersionName(OFFIS_DTK_IMPLEMENTATION_VERSION_NAME) ++ ImplementationVersionName(OFFIS_DTK_IMPLEMENTATION_VERSION_NAME), ++ MaxNestingDepth(0) + { + DcmMetaInfo *MetaInfo = new DcmMetaInfo(); + DcmSequenceOfItems::itemList->insert(MetaInfo); +@@ -71,7 +72,8 @@ + : DcmSequenceOfItems(DCM_InternalUseTag), + FileReadMode(ERM_autoDetect), + ImplementationClassUID(OFFIS_IMPLEMENTATION_CLASS_UID), +- ImplementationVersionName(OFFIS_DTK_IMPLEMENTATION_VERSION_NAME) ++ ImplementationVersionName(OFFIS_DTK_IMPLEMENTATION_VERSION_NAME), ++ MaxNestingDepth(0) + { + DcmMetaInfo *MetaInfo = new DcmMetaInfo(); + DcmSequenceOfItems::itemList->insert(MetaInfo); +@@ -99,7 +101,8 @@ + : DcmSequenceOfItems(old), + FileReadMode(old.FileReadMode), + ImplementationClassUID(old.ImplementationClassUID), +- ImplementationVersionName(old.ImplementationVersionName) ++ ImplementationVersionName(old.ImplementationVersionName), ++ MaxNestingDepth(old.MaxNestingDepth) + { + } + +@@ -128,6 +131,7 @@ + FileReadMode = obj.FileReadMode; + ImplementationClassUID = obj.ImplementationClassUID; + ImplementationVersionName = obj.ImplementationVersionName; ++ MaxNestingDepth = obj.MaxNestingDepth; + } + + return *this; +@@ -942,6 +946,9 @@ + { + /* use stdin stream */ + DcmStdinStream inStream; ++ /* apply configured nesting depth limit */ ++ if (MaxNestingDepth != 0) ++ inStream.setMaxNestingDepth(MaxNestingDepth); + + /* clear this object */ + l_error = clear(); +@@ -972,6 +979,9 @@ + } else { + /* open file for output */ + DcmInputFileStream fileStream(fileName); ++ /* apply configured nesting depth limit */ ++ if (MaxNestingDepth > 0) ++ fileStream.setMaxNestingDepth(MaxNestingDepth); + + /* check stream status */ + l_error = fileStream.status(); +--- dcmtk.orig/dcmdata/libsrc/dcistrma.cc ++++ dcmtk/dcmdata/libsrc/dcistrma.cc +@@ -1,6 +1,6 @@ + /* + * +- * Copyright (C) 1994-2010, OFFIS e.V. ++ * Copyright (C) 1994-2026, OFFIS e.V. + * All rights reserved. See COPYRIGHT file for details. + * + * This software and supporting documentation were developed by +@@ -29,6 +29,8 @@ + , compressionFilter_(NULL) + , tell_(0) + , mark_(0) ++, nestingDepth_(0) ++, maxNestingDepth_(0) + { + } + +@@ -94,6 +96,32 @@ + return current_; + } + ++Uint32 DcmInputStream::nestingDepth() const ++{ ++ return nestingDepth_; ++} ++ ++Uint32 DcmInputStream::incrementNestingDepth() ++{ ++ return ++nestingDepth_; ++} ++ ++void DcmInputStream::decrementNestingDepth() ++{ ++ if (nestingDepth_ > 0) ++ --nestingDepth_; ++} ++ ++Sint32 DcmInputStream::maxNestingDepth() const ++{ ++ return maxNestingDepth_; ++} ++ ++void DcmInputStream::setMaxNestingDepth(Sint32 maxDepth) ++{ ++ maxNestingDepth_ = maxDepth; ++} ++ + OFCondition DcmInputStream::installCompressionFilter(E_StreamCompression filterType) + { + OFCondition result = EC_Normal; +--- dcmtk.orig/dcmdata/libsrc/dcitem.cc ++++ dcmtk/dcmdata/libsrc/dcitem.cc +@@ -83,7 +83,8 @@ + elementList(NULL), + lastElementComplete(OFTrue), + fStartPosition(0), +- privateCreatorCache() ++ privateCreatorCache(), ++ maxNestingDepth(0) + { + elementList = new DcmList; + } +@@ -95,7 +96,8 @@ + elementList(NULL), + lastElementComplete(OFTrue), + fStartPosition(0), +- privateCreatorCache() ++ privateCreatorCache(), ++ maxNestingDepth(0) + { + elementList = new DcmList; + } +@@ -106,7 +108,8 @@ + elementList(new DcmList), + lastElementComplete(old.lastElementComplete), + fStartPosition(old.fStartPosition), +- privateCreatorCache() ++ privateCreatorCache(), ++ maxNestingDepth(old.maxNestingDepth) + { + if (!old.elementList->empty()) + { +@@ -136,6 +139,7 @@ + // copy DcmItem's member variables + lastElementComplete = obj.lastElementComplete; + fStartPosition = obj.fStartPosition; ++ maxNestingDepth = obj.maxNestingDepth; + if (!obj.elementList->empty()) + { + elementList->seek(ELP_first); +@@ -1394,6 +1398,10 @@ + return errorFlag; + } + ++ /* apply configured nesting depth limit to the input stream (0 = use default, skip override) */ ++ if (getMaxNestingDepth() != 0) ++ inStream.setMaxNestingDepth(getMaxNestingDepth()); ++ + /* figure out if the stream reported an error */ + errorFlag = inStream.status(); + /* if the stream reported an error or if it is the end of the */ +--- dcmtk.orig/dcmdata/libsrc/dcsequen.cc ++++ dcmtk/dcmdata/libsrc/dcsequen.cc +@@ -1,6 +1,6 @@ + /* + * +- * Copyright (C) 1994-2023, OFFIS e.V. ++ * Copyright (C) 1994-2026, OFFIS e.V. + * All rights reserved. See COPYRIGHT file for details. + * + * This software and supporting documentation were developed by +@@ -704,6 +704,23 @@ + errorFlag = EC_IllegalCall; + else + { ++ const Uint32 depth = inStream.incrementNestingDepth(); ++ const Sint32 maxDepthSetting = inStream.maxNestingDepth(); ++ /* -1 = unlimited; 0 = use built-in default (DCMTK_MAX_SEQUENCE_NESTING); > 0 = custom limit */ ++ const Uint32 effectiveMax = (maxDepthSetting == 0) ? DCMTK_MAX_SEQUENCE_NESTING ++ : (maxDepthSetting < 0) ? 0 /* unlimited */ ++ : OFstatic_cast(Uint32, maxDepthSetting); ++ if (effectiveMax > 0 && depth > effectiveMax) ++ { ++ DCMDATA_ERROR("DcmSequenceOfItems: Maximum nesting depth (" << effectiveMax ++ << ") exceeded while parsing sequence " << getTagName() << " " << getTag()); ++ inStream.decrementNestingDepth(); ++ errorFlag = EC_NestingDepthLimitExceeded; ++ // dump information if required ++ DCMDATA_TRACE("DcmSequenceOfItems::read() returns error = " << errorFlag.text()); ++ return errorFlag; ++ } ++ + errorFlag = inStream.status(); + + if (errorFlag.good() && inStream.eos()) +@@ -773,6 +790,7 @@ + errorFlag = EC_Normal; + if (errorFlag.good()) + setTransferState(ERW_ready); // sequence is complete ++ inStream.decrementNestingDepth(); + } + // dump information if required + DCMDATA_TRACE("DcmSequenceOfItems::read() returns error = " << errorFlag.text()); +--- dcmtk.orig/dcmdata/tests/CMakeLists.txt ++++ dcmtk/dcmdata/tests/CMakeLists.txt +@@ -11,6 +11,7 @@ + ti2dbmp.cc + titem.cc + tmatch.cc ++ tnesting.cc + tnewdcme.cc + tparent.cc + tparser.cc +--- dcmtk.orig/dcmdata/tests/Makefile.dep ++++ dcmtk/dcmdata/tests/Makefile.dep +@@ -712,6 +712,73 @@ + ../include/dcmtk/dcmdata/dcvr.h \ + ../../ofstd/include/dcmtk/ofstd/ofglobal.h \ + ../include/dcmtk/dcmdata/dcmatch.h ++tnesting.o: tnesting.cc ../../config/include/dcmtk/config/osconfig.h \ ++ ../../ofstd/include/dcmtk/ofstd/oftest.h \ ++ ../../ofstd/include/dcmtk/ofstd/ofconapp.h \ ++ ../../ofstd/include/dcmtk/ofstd/oftypes.h \ ++ ../../ofstd/include/dcmtk/ofstd/ofdefine.h \ ++ ../../ofstd/include/dcmtk/ofstd/ofcast.h \ ++ ../../ofstd/include/dcmtk/ofstd/ofexport.h \ ++ ../../ofstd/include/dcmtk/ofstd/ofstdinc.h \ ++ ../../ofstd/include/dcmtk/ofstd/ofcmdln.h \ ++ ../../ofstd/include/dcmtk/ofstd/ofexbl.h \ ++ ../../ofstd/include/dcmtk/ofstd/oftraits.h \ ++ ../../ofstd/include/dcmtk/ofstd/oflist.h \ ++ ../../ofstd/include/dcmtk/ofstd/ofstring.h \ ++ ../../ofstd/include/dcmtk/ofstd/ofstream.h \ ++ ../../ofstd/include/dcmtk/ofstd/ofconsol.h \ ++ ../../ofstd/include/dcmtk/ofstd/ofthread.h \ ++ ../../ofstd/include/dcmtk/ofstd/offile.h \ ++ ../../ofstd/include/dcmtk/ofstd/ofstd.h \ ++ ../../ofstd/include/dcmtk/ofstd/ofcond.h \ ++ ../../ofstd/include/dcmtk/ofstd/ofdiag.h \ ++ ../../ofstd/include/dcmtk/ofstd/diag/push.def \ ++ ../../ofstd/include/dcmtk/ofstd/diag/useafree.def \ ++ ../../ofstd/include/dcmtk/ofstd/diag/pop.def \ ++ ../../ofstd/include/dcmtk/ofstd/oflimits.h \ ++ ../../ofstd/include/dcmtk/ofstd/oferror.h \ ++ ../../ofstd/include/dcmtk/ofstd/ofexit.h \ ++ ../include/dcmtk/dcmdata/dcuid.h ../include/dcmtk/dcmdata/dcdefine.h \ ++ ../../oflog/include/dcmtk/oflog/oflog.h \ ++ ../../oflog/include/dcmtk/oflog/logger.h \ ++ ../../oflog/include/dcmtk/oflog/config.h \ ++ ../../oflog/include/dcmtk/oflog/config/defines.h \ ++ ../../oflog/include/dcmtk/oflog/helpers/threadcf.h \ ++ ../../oflog/include/dcmtk/oflog/loglevel.h \ ++ ../../ofstd/include/dcmtk/ofstd/ofvector.h \ ++ ../../oflog/include/dcmtk/oflog/tstring.h \ ++ ../../oflog/include/dcmtk/oflog/tchar.h \ ++ ../../oflog/include/dcmtk/oflog/spi/apndatch.h \ ++ ../../oflog/include/dcmtk/oflog/appender.h \ ++ ../../ofstd/include/dcmtk/ofstd/ofmem.h \ ++ ../../ofstd/include/dcmtk/ofstd/ofutil.h \ ++ ../../ofstd/include/dcmtk/ofstd/variadic/tuplefwd.h \ ++ ../../oflog/include/dcmtk/oflog/layout.h \ ++ ../../oflog/include/dcmtk/oflog/streams.h \ ++ ../../oflog/include/dcmtk/oflog/helpers/pointer.h \ ++ ../../oflog/include/dcmtk/oflog/thread/syncprim.h \ ++ ../../oflog/include/dcmtk/oflog/spi/filter.h \ ++ ../../oflog/include/dcmtk/oflog/helpers/lockfile.h \ ++ ../../oflog/include/dcmtk/oflog/spi/logfact.h \ ++ ../../oflog/include/dcmtk/oflog/logmacro.h \ ++ ../../oflog/include/dcmtk/oflog/helpers/snprintf.h \ ++ ../../oflog/include/dcmtk/oflog/tracelog.h \ ++ ../../ofstd/include/dcmtk/ofstd/oftempf.h \ ++ ../include/dcmtk/dcmdata/dcdatset.h ../include/dcmtk/dcmdata/dcitem.h \ ++ ../include/dcmtk/dcmdata/dctypes.h ../include/dcmtk/dcmdata/dcobject.h \ ++ ../../ofstd/include/dcmtk/ofstd/ofglobal.h \ ++ ../include/dcmtk/dcmdata/dcerror.h ../include/dcmtk/dcmdata/dcxfer.h \ ++ ../include/dcmtk/dcmdata/dcvr.h \ ++ ../../ofstd/include/dcmtk/ofstd/ofdeprec.h \ ++ ../include/dcmtk/dcmdata/dctag.h ../include/dcmtk/dcmdata/dctagkey.h \ ++ ../../ofstd/include/dcmtk/ofstd/diag/ignrattr.def \ ++ ../include/dcmtk/dcmdata/dcstack.h ../include/dcmtk/dcmdata/dclist.h \ ++ ../include/dcmtk/dcmdata/dcpcache.h ../include/dcmtk/dcmdata/dcfilefo.h \ ++ ../include/dcmtk/dcmdata/dcsequen.h ../include/dcmtk/dcmdata/dcelem.h \ ++ ../include/dcmtk/dcmdata/dcistrmb.h ../include/dcmtk/dcmdata/dcistrma.h \ ++ ../include/dcmtk/dcmdata/dcostrmb.h ../include/dcmtk/dcmdata/dcostrma.h \ ++ ../include/dcmtk/dcmdata/dcdeftag.h ../include/dcmtk/dcmdata/dcwcache.h \ ++ ../include/dcmtk/dcmdata/dcfcache.h + tnewdcme.o: tnewdcme.cc ../../config/include/dcmtk/config/osconfig.h \ + ../../ofstd/include/dcmtk/ofstd/oftest.h \ + ../../ofstd/include/dcmtk/ofstd/ofconapp.h \ +--- dcmtk.orig/dcmdata/tests/Makefile.in ++++ dcmtk/dcmdata/tests/Makefile.in +@@ -25,7 +25,7 @@ + + objs = tests.o tpread.o ti2dbmp.o tchval.o tpath.o tvrdatim.o telemlen.o tparser.o \ + tdict.o tvrds.o tvrfd.o tvrui.o tvrol.o tvrov.o tvrsv.o tvruv.o tstrval.o \ +- tspchrs.o tvrpn.o tparent.o tfilter.o tvrcomp.o tmatch.o tnewdcme.o \ ++ tspchrs.o tvrpn.o tparent.o tfilter.o tvrcomp.o tmatch.o tnesting.o tnewdcme.o \ + tgenuid.o tsequen.o titem.o ttag.o txfer.o tbytestr.o tfrmsiz.o + + progs = tests +--- dcmtk.orig/dcmdata/tests/tests.cc ++++ dcmtk/dcmdata/tests/tests.cc +@@ -1,6 +1,6 @@ + /* + * +- * Copyright (C) 2011-2025 OFFIS e.V. ++ * Copyright (C) 2011-2026 OFFIS e.V. + * All rights reserved. See COPYRIGHT file for details. + * + * This software and supporting documentation were developed by +@@ -127,5 +127,12 @@ + OFTEST_REGISTER(dcmdata_xferLookup_4); + OFTEST_REGISTER(dcmdata_putOFStringAtPos); + OFTEST_REGISTER(dcmdata_uncompressedFrameSize); ++OFTEST_REGISTER(dcmdata_nestingDepthLimit_exceeded); ++OFTEST_REGISTER(dcmdata_nestingDepthLimit_atLimit); ++OFTEST_REGISTER(dcmdata_nestingDepthLimit_wellBelow); ++OFTEST_REGISTER(dcmdata_nestingDepthLimit_customLimit); ++OFTEST_REGISTER(dcmdata_nestingDepthLimit_disabled); ++OFTEST_REGISTER(dcmdata_nestingDepthLimit_datasetAPI); ++OFTEST_REGISTER(dcmdata_nestingDepthLimit_fileFormatAPI); + + OFTEST_MAIN("dcmdata") +--- /dev/null ++++ dcmtk/dcmdata/tests/tnesting.cc +@@ -0,0 +1,266 @@ ++/* ++ * ++ * Copyright (C) 2026, OFFIS e.V. ++ * All rights reserved. See COPYRIGHT file for details. ++ * ++ * This software and supporting documentation were developed by ++ * ++ * OFFIS e.V. ++ * R&D Division Health ++ * Escherweg 2 ++ * D-26121 Oldenburg, Germany ++ * ++ * ++ * Module: dcmdata ++ * ++ * Author: Michael Onken ++ * ++ * Purpose: test program for sequence nesting depth limit ++ * ++ */ ++ ++ ++#include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */ ++ ++#include "dcmtk/ofstd/oftest.h" ++#include "dcmtk/ofstd/oftempf.h" ++#include "dcmtk/dcmdata/dcdatset.h" ++#include "dcmtk/dcmdata/dcfilefo.h" ++#include "dcmtk/dcmdata/dcsequen.h" ++#include "dcmtk/dcmdata/dcistrmb.h" ++#include "dcmtk/dcmdata/dcostrmb.h" ++#include "dcmtk/dcmdata/dcerror.h" ++#include "dcmtk/dcmdata/dcdeftag.h" ++#include "dcmtk/dcmdata/dcwcache.h" ++ ++ ++/** Helper: build a DcmDataset containing a chain of nested sequences to the ++ * specified depth using the high-level dcmdata API. Each nesting level ++ * consists of a DcmSequenceOfItems with one DcmItem, and the innermost ++ * item is empty. ++ * @param depth the number of nested sequence levels to generate ++ * @param dset output dataset (cleared before use) ++ */ ++static void buildNestedDataset(Uint32 depth, DcmDataset& dset) ++{ ++ dset.clear(); ++ /* start with the dataset as the outermost item */ ++ DcmItem* currentItem = &dset; ++ for (Uint32 i = 0; i < depth; ++i) ++ { ++ DcmSequenceOfItems* seq = new DcmSequenceOfItems(DCM_ReferencedSeriesSequence); ++ currentItem->insert(seq); ++ DcmItem* item = new DcmItem(); ++ seq->insert(item); ++ currentItem = item; ++ } ++} ++ ++ ++/** Helper: serialize a dataset to a byte buffer and parse it back using a ++ * fresh DcmInputStream, with an optional custom nesting depth limit. ++ * @param srcDset the dataset to serialize ++ * @param dstDset output dataset to receive the parsed result ++ * @param maxNestingDepth nesting depth setting passed to setMaxNestingDepth(): ++ * 0 (default) = compile-time default (DCMTK_MAX_SEQUENCE_NESTING), -1 = unlimited, > 0 = custom limit ++ * @return the OFCondition returned by DcmDataset::read() ++ */ ++static OFCondition serializeAndParse(DcmDataset& srcDset, DcmDataset& dstDset, ++ Sint32 maxNestingDepth = 0) ++{ ++ /* write the dataset to a memory buffer */ ++ const size_t bufLen = 1024 * 1024; ++ Uint8* buf = new Uint8[bufLen]; ++ ++ DcmOutputBufferStream outStream(buf, bufLen); ++ srcDset.transferInit(); ++ DcmWriteCache wcache; ++ OFCondition cond = srcDset.write(outStream, EXS_LittleEndianExplicit, EET_UndefinedLength, &wcache); ++ srcDset.transferEnd(); ++ if (cond.bad()) ++ { ++ delete[] buf; ++ return cond; ++ } ++ offile_off_t bytesWritten = 0; ++ void* writtenBuf = NULL; ++ outStream.flushBuffer(writtenBuf, bytesWritten); ++ ++ /* parse it back with nesting depth limit */ ++ DcmInputBufferStream inStream; ++ inStream.setBuffer(buf, bytesWritten); ++ inStream.setEos(); ++ inStream.setMaxNestingDepth(maxNestingDepth); ++ ++ dstDset.clear(); ++ dstDset.transferInit(); ++ cond = dstDset.read(inStream, EXS_LittleEndianExplicit); ++ dstDset.transferEnd(); ++ ++ delete[] buf; ++ return cond; ++} ++ ++ ++OFTEST(dcmdata_nestingDepthLimit_exceeded) ++{ ++ /* One level beyond the default limit (DCMTK_MAX_SEQUENCE_NESTING) must be rejected. */ ++ DcmDataset srcDset, dstDset; ++ buildNestedDataset(DCMTK_MAX_SEQUENCE_NESTING + 1, srcDset); ++ ++ OFCondition cond = serializeAndParse(srcDset, dstDset); ++ if (cond != EC_NestingDepthLimitExceeded) ++ { ++ OFCHECK_FAIL("Expected EC_NestingDepthLimitExceeded for " << (DCMTK_MAX_SEQUENCE_NESTING + 1) ++ << " levels of nesting, but got: " << cond.text()); ++ } ++} ++ ++ ++OFTEST(dcmdata_nestingDepthLimit_atLimit) ++{ ++ /* Exactly DCMTK_MAX_SEQUENCE_NESTING levels must still pass. */ ++ DcmDataset srcDset, dstDset; ++ buildNestedDataset(DCMTK_MAX_SEQUENCE_NESTING, srcDset); ++ ++ OFCondition cond = serializeAndParse(srcDset, dstDset); ++ if (cond.bad()) ++ { ++ OFCHECK_FAIL("Parsing " << DCMTK_MAX_SEQUENCE_NESTING ++ << " levels of nesting should succeed, but got: " << cond.text()); ++ } ++} ++ ++ ++OFTEST(dcmdata_nestingDepthLimit_wellBelow) ++{ ++ /* 5 levels of nesting (typical real-world depth) must parse fine. */ ++ DcmDataset srcDset, dstDset; ++ buildNestedDataset(5, srcDset); ++ ++ OFCondition cond = serializeAndParse(srcDset, dstDset); ++ if (cond.bad()) ++ { ++ OFCHECK_FAIL("Parsing 5 levels of nesting should succeed, but got: " << cond.text()); ++ } ++} ++ ++ ++OFTEST(dcmdata_nestingDepthLimit_customLimit) ++{ ++ /* Test setMaxNestingDepth(): set the limit to 10. ++ * 10 levels must succeed, 11 levels must fail. */ ++ DcmDataset srcDset, dstDset; ++ OFCondition cond; ++ ++ buildNestedDataset(10, srcDset); ++ cond = serializeAndParse(srcDset, dstDset, 10); ++ if (cond.bad()) ++ { ++ OFCHECK_FAIL("Parsing 10 levels with maxNestingDepth=10 should succeed, but got: " << cond.text()); ++ } ++ ++ buildNestedDataset(11, srcDset); ++ cond = serializeAndParse(srcDset, dstDset, 10); ++ if (cond != EC_NestingDepthLimitExceeded) ++ { ++ OFCHECK_FAIL("Expected EC_NestingDepthLimitExceeded for 11 levels with maxNestingDepth=10, but got: " << cond.text()); ++ } ++} ++ ++ ++OFTEST(dcmdata_nestingDepthLimit_disabled) ++{ ++ /* Test that setMaxNestingDepth(-1) disables the limit entirely. ++ * Even 200 levels of nesting must succeed. */ ++ DcmDataset srcDset, dstDset; ++ buildNestedDataset(200, srcDset); ++ ++ OFCondition cond = serializeAndParse(srcDset, dstDset, -1); ++ if (cond.bad()) ++ { ++ OFCHECK_FAIL("Parsing 200 levels with maxNestingDepth=-1 (unlimited) should succeed, but got: " << cond.text()); ++ } ++} ++ ++ ++/** Helper: create a temporary file path. ++ * @param tmpFile receives the temporary file path ++ * @return EC_Normal if successful ++ */ ++static OFCondition makeTempFile(OFString& tmpFile) ++{ ++ return OFTempFile::createFile(tmpFile, NULL /* fd_out */, O_RDWR, ++ "" /* dir */, "" /* prefix */, ".dcm" /* postfix */); ++} ++ ++ ++OFTEST(dcmdata_nestingDepthLimit_datasetAPI) ++{ ++ /* Test DcmDataset::setMaxNestingDepth() with loadFile(). */ ++ DcmDataset srcDset; ++ OFString tmpFile; ++ OFCondition cond; ++ ++ /* save as raw dataset (no meta header) */ ++ buildNestedDataset(11, srcDset); ++ cond = makeTempFile(tmpFile); ++ OFCHECK(cond.good()); ++ cond = srcDset.saveFile(tmpFile, EXS_LittleEndianExplicit); ++ OFCHECK(cond.good()); ++ ++ /* default limit (DCMTK_MAX_SEQUENCE_NESTING): 11 levels should succeed */ ++ DcmDataset dset1; ++ cond = dset1.loadFile(tmpFile, EXS_LittleEndianExplicit); ++ if (cond.bad()) ++ { ++ OFCHECK_FAIL("DcmDataset::loadFile() with 11 levels should succeed with default limit, but got: " << cond.text()); ++ } ++ ++ /* custom limit 10: 11 levels should fail */ ++ DcmDataset dset2; ++ dset2.setMaxNestingDepth(10); ++ cond = dset2.loadFile(tmpFile, EXS_LittleEndianExplicit); ++ if (cond != EC_NestingDepthLimitExceeded) ++ { ++ OFCHECK_FAIL("DcmDataset::loadFile() with 11 levels and maxNestingDepth=10 should fail, but got: " << cond.text()); ++ } ++ ++ OFStandard::deleteFile(tmpFile); ++} ++ ++ ++OFTEST(dcmdata_nestingDepthLimit_fileFormatAPI) ++{ ++ /* Test DcmFileFormat::setMaxNestingDepth() with loadFile(). */ ++ DcmDataset srcDset; ++ OFString tmpFile; ++ OFCondition cond; ++ ++ /* save as DcmFileFormat (with meta header) */ ++ buildNestedDataset(11, srcDset); ++ cond = makeTempFile(tmpFile); ++ OFCHECK(cond.good()); ++ DcmFileFormat srcFF(&srcDset); ++ cond = srcFF.saveFile(tmpFile, EXS_LittleEndianExplicit); ++ OFCHECK(cond.good()); ++ ++ /* default limit (DCMTK_MAX_SEQUENCE_NESTING): 11 levels should succeed */ ++ DcmFileFormat ff1; ++ cond = ff1.loadFile(tmpFile); ++ if (cond.bad()) ++ { ++ OFCHECK_FAIL("DcmFileFormat::loadFile() with 11 levels should succeed with default limit, but got: " << cond.text()); ++ } ++ ++ /* custom limit 10: 11 levels should fail */ ++ DcmFileFormat ff2; ++ ff2.setMaxNestingDepth(10); ++ cond = ff2.loadFile(tmpFile); ++ if (cond != EC_NestingDepthLimitExceeded) ++ { ++ OFCHECK_FAIL("DcmFileFormat::loadFile() with 11 levels and maxNestingDepth=10 should fail, but got: " << cond.text()); ++ } ++ ++ OFStandard::deleteFile(tmpFile); ++} +--- dcmtk.orig/dcmnet/include/dcmtk/dcmnet/scp.h ++++ dcmtk/dcmnet/include/dcmtk/dcmnet/scp.h +@@ -360,6 +360,17 @@ + */ + void setAlwaysAcceptDefaultRole(const OFBool enabled); + ++ /** Set the maximum permitted sequence nesting depth for parsing ++ * datasets received over the network. This limit is applied to ++ * each dataset received via receiveDIMSEDataset(). ++ * - Value 0 (default): apply the compile-time default ++ * (DCMTK_MAX_SEQUENCE_NESTING) ++ * - Value -1: disable the check (allow unlimited nesting) ++ * - Value > 0: use this value as the maximum nesting depth ++ * @param maxDepth maximum nesting depth setting ++ */ ++ void setMaxNestingDepth(const Sint32 maxDepth); ++ + /* Get methods for SCP settings */ + + /** Returns TCP/IP port number SCP listens for new connection requests +@@ -434,6 +445,14 @@ + */ + OFBool getProgressNotificationMode() const; + ++ /** Return the maximum permitted sequence nesting depth for ++ * parsing datasets received over the network. ++ * @return maximum nesting depth setting ++ * (0 = compile-time default DCMTK_MAX_SEQUENCE_NESTING, ++ * -1 = unlimited) ++ */ ++ Sint32 getMaxNestingDepth() const; ++ + /** Get access to the configuration of the SCP. Note that the functionality + * on the configuration object is shadowed by other API functions of DcmSCP. + * The existing functions are provided in order to not break users of this +@@ -1157,6 +1176,11 @@ + /// it, e.g. in the context of the DcmSCPPool class. + DcmSharedSCPConfig m_cfg; + ++ /// Maximum sequence nesting depth for parsing received datasets ++ /// (0 = compile-time default DCMTK_MAX_SEQUENCE_NESTING, ++ /// -1 = unlimited) ++ Sint32 m_maxNestingDepth; ++ + /** Drops association and clears internal structures to free memory + */ + void dropAndDestroyAssociation(); +--- dcmtk.orig/dcmnet/include/dcmtk/dcmnet/scu.h ++++ dcmtk/dcmnet/include/dcmtk/dcmnet/scu.h +@@ -739,6 +739,17 @@ + */ + void setProgressNotificationMode(const OFBool mode); + ++ /** Set the maximum permitted sequence nesting depth for parsing ++ * datasets received over the network. This limit is applied to ++ * each dataset received via receiveDIMSEDataset(). ++ * - Value 0 (default): apply the compile-time default ++ * (DCMTK_MAX_SEQUENCE_NESTING) ++ * - Value -1: disable the check (allow unlimited nesting) ++ * - Value > 0: use this value as the maximum nesting depth ++ * @param maxDepth maximum nesting depth setting ++ */ ++ void setMaxNestingDepth(const Sint32 maxDepth); ++ + /* Get methods */ + + /** Get current connection status +@@ -831,6 +842,14 @@ + */ + OFBool getProgressNotificationMode() const; + ++ /** Return the maximum permitted sequence nesting depth for ++ * parsing datasets received over the network. ++ * @return maximum nesting depth setting ++ * (0 = compile-time default DCMTK_MAX_SEQUENCE_NESTING, ++ * -1 = unlimited) ++ */ ++ Sint32 getMaxNestingDepth() const; ++ + /** Returns whether SCU is configured to create a TLS connection with the SCP + * @return OFTrue if TLS mode has been enabled, OFFalse otherwise + */ +@@ -1136,6 +1155,11 @@ + /// Progress notification mode (default: enabled) + OFBool m_progressNotificationMode; + ++ /// Maximum sequence nesting depth for parsing received datasets ++ /// (0 = compile-time default DCMTK_MAX_SEQUENCE_NESTING, ++ /// -1 = unlimited) ++ Sint32 m_maxNestingDepth; ++ + /// Flag indicating whether secure mode has been enabled (default: disabled) + OFBool m_secureConnectionEnabled; + +--- dcmtk.orig/dcmnet/libsrc/dimse.cc ++++ dcmtk/dcmnet/libsrc/dimse.cc +@@ -1,6 +1,6 @@ + /* + * +- * Copyright (C) 1994-2025, OFFIS e.V. ++ * Copyright (C) 1994-2026, OFFIS e.V. + * All rights reserved. See COPYRIGHT file for details. + * + * This software and supporting documentation were partly developed by +@@ -1536,7 +1536,8 @@ + dset = *dataObject; + } + +- /* check if there is still no DcmDataset object which can be used to store the data set. */ ++ /* check if there is still no DcmDataset object (i.e. if allocation fails) */ ++ /* which can be used to store the data set. */ + if (dset == NULL) + { + /* if this is the case, just go ahead an receive data, but do not store it anywhere */ +--- dcmtk.orig/dcmnet/libsrc/scp.cc ++++ dcmtk/dcmnet/libsrc/scp.cc +@@ -32,6 +32,7 @@ + : m_network(NULL) + , m_assoc(NULL) + , m_cfg() ++, m_maxNestingDepth(0) + { + OFStandard::initializeNetwork(); + } +@@ -1590,6 +1591,19 @@ + if (m_assoc == NULL) + return DIMSE_ILLEGALASSOCIATION; + ++ /* if a custom nesting depth limit is configured, pre-allocate ++ * the dataset and apply the setting before parsing starts. ++ * DIMSE_receiveDataSetInMemory() will use this dataset instead ++ * of creating a new one internally. */ ++ OFBool weAllocated = OFFalse; ++ if (dataObject != NULL && *dataObject == NULL ++ && m_maxNestingDepth != 0) ++ { ++ *dataObject = new DcmDataset(); ++ (*dataObject)->setMaxNestingDepth(m_maxNestingDepth); ++ weAllocated = OFTrue; ++ } ++ + OFCondition cond; + /* call the corresponding DIMSE function to receive the dataset */ + if (m_cfg->getProgressNotificationMode()) +@@ -1619,6 +1633,14 @@ + } + else + { ++ /* if we pre-allocated the dataset, clean up on error since ++ * DIMSE_receiveDataSetInMemory() won't delete a dataset ++ * that was passed in by the caller */ ++ if (weAllocated && dataObject != NULL) ++ { ++ delete *dataObject; ++ *dataObject = NULL; ++ } + OFString tempStr; + DCMNET_ERROR("Unable to receive dataset on presentation context " + << OFstatic_cast(unsigned int, *presID) << ": " << DimseCondition::dump(tempStr, cond)); +@@ -1910,6 +1932,13 @@ + + // ---------------------------------------------------------------------------- + ++void DcmSCP::setMaxNestingDepth(const Sint32 maxDepth) ++{ ++ m_maxNestingDepth = maxDepth; ++} ++ ++// ---------------------------------------------------------------------------- ++ + /* Get methods for SCP settings and current association information */ + + OFBool DcmSCP::getRefuseAssociation() const +@@ -2002,6 +2031,13 @@ + } + + // ---------------------------------------------------------------------------- ++ ++Sint32 DcmSCP::getMaxNestingDepth() const ++{ ++ return m_maxNestingDepth; ++} ++ ++// ---------------------------------------------------------------------------- + + OFBool DcmSCP::isConnected() const + { +--- dcmtk.orig/dcmnet/libsrc/scu.cc ++++ dcmtk/dcmnet/libsrc/scu.cc +@@ -56,6 +56,7 @@ + , m_verbosePCMode(OFFalse) + , m_datasetConversionMode(OFFalse) + , m_progressNotificationMode(OFTrue) ++ , m_maxNestingDepth(0) + , m_secureConnectionEnabled(OFFalse) + , m_protocolVersion(ASC_AF_Default) + { +@@ -2500,6 +2501,19 @@ + if (!isConnected()) + return DIMSE_ILLEGALASSOCIATION; + ++ /* if a custom nesting depth limit is configured, pre-allocate ++ * the dataset and apply the setting before parsing starts. ++ * DIMSE_receiveDataSetInMemory() will use this dataset instead ++ * of creating a new one internally. */ ++ OFBool weAllocated = OFFalse; ++ if (dataObject != NULL && *dataObject == NULL ++ && m_maxNestingDepth != 0) ++ { ++ *dataObject = new DcmDataset(); ++ (*dataObject)->setMaxNestingDepth(m_maxNestingDepth); ++ weAllocated = OFTrue; ++ } ++ + OFCondition cond; + /* call the corresponding DIMSE function to receive the dataset */ + if (m_progressNotificationMode) +@@ -2519,6 +2533,14 @@ + } + else + { ++ /* if we pre-allocated the dataset, clean up on error since ++ * DIMSE_receiveDataSetInMemory() won't delete a dataset ++ * that was passed in by the caller */ ++ if (weAllocated && dataObject != NULL) ++ { ++ delete *dataObject; ++ *dataObject = NULL; ++ } + OFString tempStr; + DCMNET_ERROR("Unable to receive dataset on presentation context " + << OFstatic_cast(unsigned int, *presID) << ": " << DimseCondition::dump(tempStr, cond)); +@@ -2607,6 +2629,11 @@ + m_progressNotificationMode = mode; + } + ++void DcmSCU::setMaxNestingDepth(const Sint32 maxDepth) ++{ ++ m_maxNestingDepth = maxDepth; ++} ++ + void DcmSCU::setProtocolVersion(T_ASC_ProtocolFamily protocolVersion) + { + m_protocolVersion = protocolVersion; +@@ -2694,6 +2721,11 @@ + return m_progressNotificationMode; + } + ++Sint32 DcmSCU::getMaxNestingDepth() const ++{ ++ return m_maxNestingDepth; ++} ++ + OFCondition DcmSCU::getDatasetInfo(DcmDataset* dataset, + OFString& sopClassUID, + OFString& sopInstanceUID, +--- dcmtk.orig/dcmnet/tests/tests.cc ++++ dcmtk/dcmnet/tests/tests.cc +@@ -51,6 +51,11 @@ + OFTEST_REGISTER(dcmnet_scu_sendNSETRequest_succeeds_and_modifies_instance_when_scp_has_instance); + OFTEST_REGISTER(dcmnet_scu_sendNSETRequest_succeeds_and_sets_responsestatuscode_from_scp_when_scp_sets_error_status); + ++OFTEST_REGISTER(dcmnet_scp_maxNestingDepth_getset); ++OFTEST_REGISTER(dcmnet_scu_maxNestingDepth_getset); ++OFTEST_REGISTER(dcmnet_scp_maxNestingDepth_rejects_deep); ++OFTEST_REGISTER(dcmnet_scp_maxNestingDepth_accepts_shallow); ++ + #endif // WITH_THREADS + + OFTEST_MAIN("dcmnet") +--- dcmtk.orig/dcmnet/tests/tscuscp.cc ++++ dcmtk/dcmnet/tests/tscuscp.cc +@@ -1113,4 +1113,209 @@ + } + + ++// Test that setMaxNestingDepth/getMaxNestingDepth work on DcmSCP ++OFTEST(dcmnet_scp_maxNestingDepth_getset) ++{ ++ DcmSCP scp; ++ // Default must be 0 (use compile-time default) ++ OFCHECK_EQUAL(scp.getMaxNestingDepth(), 0); ++ scp.setMaxNestingDepth(10); ++ OFCHECK_EQUAL(scp.getMaxNestingDepth(), 10); ++ scp.setMaxNestingDepth(-1); ++ OFCHECK_EQUAL(scp.getMaxNestingDepth(), -1); ++ scp.setMaxNestingDepth(0); ++ OFCHECK_EQUAL(scp.getMaxNestingDepth(), 0); ++} ++ ++ ++// Test that setMaxNestingDepth/getMaxNestingDepth work on DcmSCU ++OFTEST(dcmnet_scu_maxNestingDepth_getset) ++{ ++ DcmSCU scu; ++ // Default must be 0 (use compile-time default) ++ OFCHECK_EQUAL(scu.getMaxNestingDepth(), 0); ++ scu.setMaxNestingDepth(5); ++ OFCHECK_EQUAL(scu.getMaxNestingDepth(), 5); ++ scu.setMaxNestingDepth(-1); ++ OFCHECK_EQUAL(scu.getMaxNestingDepth(), -1); ++ scu.setMaxNestingDepth(0); ++ OFCHECK_EQUAL(scu.getMaxNestingDepth(), 0); ++} ++ ++ ++/** Helper: build a DcmDataset with nested sequences to a given depth. ++ * @param depth number of nesting levels ++ * @param dset output dataset (cleared before use) ++ */ ++static void buildNestedDataset(Uint32 depth, DcmDataset& dset) ++{ ++ dset.clear(); ++ DcmItem* currentItem = &dset; ++ for (Uint32 i = 0; i < depth; ++i) ++ { ++ DcmSequenceOfItems* sq = ++ new DcmSequenceOfItems(DCM_ReferencedSeriesSequence); ++ currentItem->insert(sq); ++ DcmItem* item = new DcmItem(); ++ sq->insert(item); ++ currentItem = item; ++ } ++} ++ ++ ++/** SCP that applies a nesting depth limit and records whether ++ * receiving the dataset succeeded or failed. ++ */ ++struct NestingTestSCP : TestSCP ++{ ++ NestingTestSCP(Sint32 maxNesting) ++ : TestSCP() ++ , m_receiveResult(EC_NotYetImplemented) ++ { ++ setMaxNestingDepth(maxNesting); ++ DcmSCPConfig& config = getConfig(); ++ config.setAETitle("NESTING_SCP"); ++ config.setConnectionBlockingMode(DUL_NOBLOCK); ++ config.setConnectionTimeout(10); ++ config.setHostLookupEnabled(OFFalse); ++ config.setPort(0); ++ OFList xfers; ++ xfers.push_back(UID_LittleEndianImplicitTransferSyntax); ++ OFCHECK(config.addPresentationContext( ++ UID_ModalityPerformedProcedureStepSOPClass, xfers).good()); ++ OFCHECK(openListenPort().good()); ++ m_portNum = config.getPort(); ++ } ++ ++ OFCondition handleIncomingCommand( ++ T_DIMSE_Message* incomingMsg, ++ const DcmPresentationContextInfo& presInfo) ++ { ++ if (incomingMsg->CommandField == DIMSE_N_CREATE_RQ) ++ { ++ T_DIMSE_N_CreateRQ& req = incomingMsg->msg.NCreateRQ; ++ if (req.DataSetType != DIMSE_DATASET_NULL) ++ { ++ DcmDataset* dataset = OFnullptr; ++ T_ASC_PresentationContextID presIDdset; ++ m_receiveResult = ++ receiveDIMSEDataset(&presIDdset, &dataset); ++ /* send a response regardless of receive outcome */ ++ T_DIMSE_Message rsp; ++ memset(&rsp, 0, sizeof(rsp)); ++ rsp.CommandField = DIMSE_N_CREATE_RSP; ++ T_DIMSE_N_CreateRSP& createRsp = rsp.msg.NCreateRSP; ++ createRsp.MessageIDBeingRespondedTo = req.MessageID; ++ createRsp.DimseStatus = m_receiveResult.good() ++ ? STATUS_N_Success ++ : STATUS_N_ProcessingFailure; ++ createRsp.DataSetType = DIMSE_DATASET_NULL; ++ OFStandard::strlcpy( ++ createRsp.AffectedSOPClassUID, ++ req.AffectedSOPClassUID, ++ sizeof(createRsp.AffectedSOPClassUID)); ++ createRsp.opts = O_NCREATE_AFFECTEDSOPCLASSUID; ++ sendDIMSEMessage( ++ presInfo.presentationContextID, &rsp, NULL); ++ delete dataset; ++ } ++ return EC_Normal; ++ } ++ return DcmSCP::handleIncomingCommand(incomingMsg, presInfo); ++ } ++ ++ /// Result of receiveDIMSEDataset() for the last request ++ OFCondition m_receiveResult; ++ /// Port the SCP is listening on ++ Uint16 m_portNum; ++}; ++ ++ ++// Test that SCP's nesting depth limit rejects deeply nested data ++// received over the network ++OFTEST_FLAGS(dcmnet_scp_maxNestingDepth_rejects_deep, EF_Slow) ++{ ++ NestingTestSCP scp(3); ++ scp.m_set_stop_after_assoc = OFTrue; ++ scp.start(); ++ OFStandard::forceSleep(1); ++ ++ // SCU sends a dataset with 4 levels of nesting (exceeds limit 3) ++ DcmSCU scu; ++ scu.setAETitle("NESTING_SCU"); ++ scu.setPeerAETitle("NESTING_SCP"); ++ scu.setPeerHostName("localhost"); ++ scu.setPeerPort(scp.m_portNum); ++ OFList xfers; ++ xfers.push_back(UID_LittleEndianImplicitTransferSyntax); ++ OFCHECK(scu.addPresentationContext( ++ UID_ModalityPerformedProcedureStepSOPClass, xfers).good()); ++ OFCHECK(scu.initNetwork().good()); ++ OFCHECK(scu.negotiateAssociation().good()); ++ ++ DcmDataset reqDataset; ++ buildNestedDataset(4, reqDataset); ++ T_ASC_PresentationContextID presID = scu.findPresentationContextID( ++ UID_ModalityPerformedProcedureStepSOPClass, ++ UID_LittleEndianImplicitTransferSyntax); ++ OFCHECK(presID != 0); ++ Uint16 rspStatus = 0; ++ DcmDataset* rspDataset = NULL; ++ scu.sendNCREATERequest(presID, "1.2.3.4.5", ++ &reqDataset, rspDataset, rspStatus); ++ delete rspDataset; ++ if (scu.isConnected()) ++ scu.releaseAssociation(); ++ OFStandard::forceSleep(2); ++ scp.join(); ++ ++ // Verify SCP saw the nesting depth error ++ OFCHECK(scp.m_receiveResult == DIMSE_RECEIVEFAILED); ++} ++ ++ ++// Test that SCP's nesting depth limit accepts data within the limit ++OFTEST_FLAGS(dcmnet_scp_maxNestingDepth_accepts_shallow, EF_Slow) ++{ ++ NestingTestSCP scp(3); ++ scp.m_set_stop_after_assoc = OFTrue; ++ scp.start(); ++ OFStandard::forceSleep(1); ++ ++ // SCU sends a dataset with 3 levels of nesting (at the limit) ++ DcmSCU scu; ++ scu.setAETitle("NESTING_SCU"); ++ scu.setPeerAETitle("NESTING_SCP"); ++ scu.setPeerHostName("localhost"); ++ scu.setPeerPort(scp.m_portNum); ++ OFList xfers; ++ xfers.push_back(UID_LittleEndianImplicitTransferSyntax); ++ OFCHECK(scu.addPresentationContext( ++ UID_ModalityPerformedProcedureStepSOPClass, xfers).good()); ++ OFCHECK(scu.initNetwork().good()); ++ OFCHECK(scu.negotiateAssociation().good()); ++ ++ DcmDataset reqDataset; ++ buildNestedDataset(3, reqDataset); ++ T_ASC_PresentationContextID presID = scu.findPresentationContextID( ++ UID_ModalityPerformedProcedureStepSOPClass, ++ UID_LittleEndianImplicitTransferSyntax); ++ OFCHECK(presID != 0); ++ Uint16 rspStatus = 0; ++ DcmDataset* rspDataset = NULL; ++ OFCondition result = scu.sendNCREATERequest(presID, "1.2.3.4.5", ++ &reqDataset, rspDataset, rspStatus); ++ OFCHECK_MSG(result.good(), result.text()); ++ OFCHECK(rspStatus == STATUS_N_Success); ++ delete rspDataset; ++ if (scu.isConnected()) ++ OFCHECK(scu.releaseAssociation().good()); ++ OFStandard::forceSleep(2); ++ scp.join(); ++ ++ // Verify SCP successfully received the dataset ++ OFCHECK(scp.m_receiveResult.good()); ++} ++ ++ + #endif // WITH_THREADS diff --git a/debian/patches/series b/debian/patches/series index 910203c1..56c3d886 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -5,3 +5,4 @@ remove_version.patch skip-bigendian-roundtrip-failure.patch hurd.patch CVE-2026-5663.patch +CVE-2026-10528-partial.patch -- 2.30.2